/* compute ones-compliment checksum of a packet */

/* Copyright (C) 2009-2011 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/* could be extended to use hardware DMA checksum feature of 
 * the enc28j60, but .. */

/* only need basic stuff for this */
#include <stdio.h>
#include "checksum.h"
#include <string.h>

uint16_t checksum(uint8_t *pkt, uint16_t len) {
 uint32_t sum = 0;
 while (len) {
  sum += (*pkt) << 8;
  pkt++;
  sum += (*pkt);
  pkt++;
  len -= 2;
 }
 /* colpase the sum into a 16-bit value */
 while (sum >> 16) {
  sum = (sum & 0xffff) + (sum >> 16);
 }
 sum = ~sum;
 return sum;
} 

uint16_t udp_checksum(uint8_t *pkt, uint16_t length, uint32_t src, 
                      uint32_t dst) {
 uint32_t sum = 0;
 uint16_t len = length;
 uint8_t addr[4];

 while (len > 1) {
  sum += 0xffff & (*pkt << 8 | *(pkt+1));
  pkt += 2;
  len -= 2;
 }
 
 if (len) {
  sum += (0xff & *pkt) << 8; /* plus a trailing 0 */
 } 

 /* now the psudoheader */
 memcpy(&addr,&src,4);

 sum += 0xffff & (addr[0] << 8 | addr[1]);
 sum += 0xffff & (addr[2] << 8 | addr[3]);
 
 memcpy(&addr,&dst,4);

 sum += 0xffff & (addr[0] << 8 | addr[1]);
 sum += 0xffff & (addr[2] << 8 | addr[3]);

 sum += 0xffff & 17; /* UDP protcol */
 sum += 0xffff & length;

 /* and the final carries */
 while (sum >> 16) {
  sum = (sum & 0xffff) + (sum >> 16);
 }

 return ( (uint16_t) (sum ^ 0xffff) );

}
 
uint32_t checksum_partial(uint8_t *pkt, int16_t len, uint32_t sum) {

 if (len == -1) {
  while (sum >> 16) {
   sum = (sum & 0xffff) + (sum >> 16);
  }
  sum = (~sum & 0xffff);
  return sum;
 }

 while (len > 1) {
  sum += (*pkt) << 8;
  pkt++;
  sum += (*pkt);
  pkt++;
  len -= 2;
 }

 return sum;
}
